#include "gwbase/base/logging.h"
#include "gwbase/base/timestamp.h"
#include "gwbase/base/thread.h"
#include "gwbase/base/mutex.h"
#include "gwbase/base/file_ops.h"
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <assert.h>
#include <stdio.h>

namespace dicom {
namespace {

const char* kLogLevelChar[] = {"TRACE ", "DEBUG ", "INFO  ",
                               "WARN  ", "ERROR ", "FATAL ", };

inline const char* GetLogLevelStr(LogLevel level) {
  return kLogLevelChar[level];
};

class ConsoleLogSink : public LogSink {
 public:
  ConsoleLogSink() {}
  virtual ~ConsoleLogSink() {}

  virtual void WriteLog(const void* buf, size_t len) {
    fwrite(buf, 1, len, stdout);
  }

  virtual void Flush() { fflush(stdout); }
};

class RollingFileLogSink : public LogSink {
  static const size_t kRollingLimit = 1024 * 1024 * 128;  // 128 MBytes
  static const int kRollTimeLimitInSeconds = 3600 * 24;   // 24 hours
  // static const int kRollTimeLimitInSeconds = 5; // 24 hours
 public:
  RollingFileLogSink(const std::string& base_dir, const std::string& progname)
      : base_dir_(base_dir), prog_name_(progname), size_(0), fd_(-1) {
    RollFile();
  }

  virtual ~RollingFileLogSink() {
    if (fd_ != -1) ::close(fd_);
  }

  virtual void WriteLog(const void* buf, size_t len) {
    LockGuard g(mu_);
    assert(fd_ != -1);
    if (size_ + len > kRollingLimit || next_roll_time_ <= ::time(NULL)) {
      RollFile();
    }
    ::write(fd_, buf, len);
    size_ += len;
  }

  virtual void Flush() {
    LockGuard g(mu_);
    if (fd_ != -1) ::fdatasync(fd_);
  }

 private:
  void RollFile();
  std::string GetLogFileName();

  const std::string base_dir_;
  const std::string prog_name_;
  time_t next_roll_time_;
  size_t size_;
  int fd_;
  Mutex mu_;
};

std::string RollingFileLogSink::GetLogFileName() {
  Date d;
  Timestamp::Now().Breakdown(&d);
  char name[512];
  snprintf(name, 512, "%s.%04d%02d%02d-%02d%02d%02d.%05d.log",
           prog_name_.c_str(), d.year, d.month, d.day, d.hour, d.min, d.sec,
           getpid());
  std::string logpath = base_dir_ + "/" + name;
  return logpath;
}

void RollingFileLogSink::RollFile() {
  if (fd_ != -1) {
    ::fdatasync(fd_);
    ::close(fd_);
    fd_ = -1;
    size_ = 0;
  }

  std::string logpath = GetLogFileName();
  fd_ = ::creat(logpath.c_str(), 0644);
  if (fd_ < 0) {
    fprintf(stderr, "Cannot create logging file: %s\n", logpath.c_str());
    return;
  }
  next_roll_time_ = ::time(NULL) + kRollTimeLimitInSeconds;
}

LogLevel g_level = kLogInfo;
LogSink* g_sink = new ConsoleLogSink();
}

LogSink* GetCurrentSink() { return g_sink; }

std::string PosixErrorMsg(int err_num) { return strerror(err_num); }

LogEntry::LogEntry(LogLevel level, const SourceLocation& loc)
    : level_(level), loc_(loc) {
  WriteHeader();
}

void LogEntry::WriteHeader() {
  Timestamp now = Timestamp::Now();
  os_ << now.ToLogTime() << ' ' << Fmt("%5d", gettid()) << ' '
      << GetLogLevelStr(level_);
}

void LogEntry::WriteTrailer() {
  os_ << " - " << loc_.file() << ':' << loc_.line() << std::endl;
}

LogEntry::~LogEntry() {
  WriteTrailer();
  std::string msg = os_.str();
  g_sink->WriteLog(msg.c_str(), msg.size());
  if (level_ == kLogFatal) {
    g_sink->Flush();
    abort();
  }
}

void SetLogLevel(LogLevel level) { g_level = level; }

LogLevel GetLogLevel() { return g_level; }

LogSink* CreateRollingFileLogSink(const std::string& base_dir,
                                  const std::string& progname) {
  Status s = CreateDir(base_dir);
  if (!s.ok()) {
    fprintf(stderr, "Cannot create dir for logging: %s", s.ToString().c_str());
    abort();
  }

  return new RollingFileLogSink(base_dir, progname);
}

void SetRollingFileLogging(const std::string& base_dir,
                           const std::string& progname) {
  if (g_sink != NULL) delete g_sink;

  Status s = CreateDir(base_dir);
  if (!s.ok()) {
    fprintf(stderr, "Cannot create dir for logging: %s", s.ToString().c_str());
    abort();
  }

  g_sink = new RollingFileLogSink(base_dir, progname);
}
}
